package xyz.eden.gateway.springcloud.admin.component.route;

import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Component
public class DynamicRouteOperate implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher publisher;

    private final RouteDefinitionWriter routeDefinitionWriter;
    private final RouteDefinitionLocator routeDefinitionLocator;
    private final RouteDefinitionRepository routeDefinitionRepository;

    public DynamicRouteOperate(RouteDefinitionWriter routeDefinitionWriter, RouteDefinitionLocator routeDefinitionLocator, RouteDefinitionRepository routeDefinitionRepository) {
        this.routeDefinitionWriter = routeDefinitionWriter;
        this.routeDefinitionLocator = routeDefinitionLocator;
        this.routeDefinitionRepository = routeDefinitionRepository;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    /**
     * 更新路由定义
     *
     * @param list
     * @return
     */
    public boolean updateRouteDefinition(List<RouteDefinition> list) {
        log.info("[Dynamic Route] update route definition info: [{}].", list);

        List<RouteDefinition> oldDefinitionList = routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();

        /**
         * 更新新的配置
         */
        Optional.ofNullable(list).orElse(Collections.emptyList()).forEach(this::updateRouteDefinition);

        /**
         * 删除旧的配置
         */
        List<RouteDefinition> deleteRouteDefinitionList = this.getDeleteRouteDefinition(oldDefinitionList, list);
        Optional.of(deleteRouteDefinitionList).orElse(Collections.emptyList())
                .stream().map(RouteDefinition::getId).forEach(this::deleteRouteDefinitionById);
        return true;
    }

    /**
     * 获取需要删除的路由定义
     * <p>
     * 在旧中存在, 在新中不存在的则删除
     * </p>
     *
     * @param oldDefinitionList
     * @param newDefinitionList
     * @return
     */
    private List<RouteDefinition> getDeleteRouteDefinition(List<RouteDefinition> oldDefinitionList, List<RouteDefinition> newDefinitionList) {
        Map<String, RouteDefinition> map = Optional.ofNullable(newDefinitionList).orElse(Collections.emptyList())
                .stream().collect(Collectors.toMap(RouteDefinition::getId, Function.identity(), (o1, o2) -> o2));
        return Optional.ofNullable(oldDefinitionList).orElse(Collections.emptyList())
                .stream().filter(routeDefinition -> {
                    return !map.containsKey(routeDefinition.getId());
                }).collect(Collectors.toList());
    }

    /**
     * 更新路由规则
     *
     * @param definition
     * @return
     */
    public boolean updateRouteDefinition(RouteDefinition definition) {
        log.info("[Dynamic Route] update route definition, info: [{}].", definition);
        try {
            /**
             * 先删除后添加
             */
            // 删除
            try {
                this.routeDefinitionWriter.delete(Mono.just(definition.getId())).subscribe();
            } catch (Exception e) {
                log.error("[Dynamic Route] update route definition, delete id: [{}]  exception.", definition.getId(), e);
            }
            // 添加
            try {
                this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            } catch (Exception e) {
                log.error("[Dynamic Route] update route definition, save exception, info: [{}].", definition, e);
            }
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return true;
        } catch (Exception e) {
            log.error("[Dynamic Route] update route definition exception, info: [{}].", definition, e);
            return false;
        }
    }

    /**
     * 添加路由定义
     *
     * @param definitionList
     * @return
     */
    public boolean saveRouteDefinition(List<RouteDefinition> definitionList) {
        log.info("[Dynamic Route] save route definition, info: [{}].", JSON.toJSONString(definitionList));
        try {
            Optional.ofNullable(definitionList).orElse(Collections.emptyList()).forEach(this::saveRouteDefinition);
            return true;
        } catch (Exception e) {
            log.error("[Dynamic Route] save route definition exception, info: [{}].", JSON.toJSONString(definitionList), e);
            return false;
        }
    }

    /**
     * 添加路由定义
     *
     * @param definition
     * @return
     */
    public boolean saveRouteDefinition(RouteDefinition definition) {
        log.info("[Dynamic Route] save route definition, info: [{}].", definition);
        try {
            this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return true;
        } catch (Exception e) {
            log.error("[Dynamic Route] save route definition exception, info: [{}].", definition, e);
            return false;
        }
    }

    /**
     * 通过 id 删除路由定义
     *
     * @param id
     * @return
     */
    public boolean deleteRouteDefinitionById(String id) {
        log.info("[Dynamic Route] delete id: [{}] route definition.", id);
        try {
            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return true;
        } catch (Exception e) {
            log.error("[Dynamic Route] delete id: [{}] route definition exception.", id, e);
            return false;
        }
    }
}
